<?php

namespace Pinenut\Service\Dispatcher;

/**
 *
 *
 *
 * Диспечер действий, кеширующий
 *
 * Кеширование с разграничением прав досттупа использует два вида записей:
 * 1) запись со списком ключей данных (вторых записей), и
 * привилегиями (+результатами проверки привилегий) - "отпечатками прав"
 * по ним определяется какие данные доставать и есть ли они вообще
 * 2) запись с ответом в рамках привилегий
 *
 *
 *
 * @author kosh
 *        
 */
use Pinenut\Service\Dispatcher;
use Pinenut\Request;

class Caching extends Dispatcher {
	static protected function doAction(Request $request, $response, $skipStages = 0) {
		// накапливаем тэги на зачистку
		// тут а не в канале потому как накапливать не будем, надо все ёк
//		$cleanTags = [];
		
		// обычное действие
		$stages = $request->getStages();
		// проверяем в кеш
		$key = md5 ( serialize ( $stages ) ) . $request->getCacheKey();

		if (!($response = static::fromCache ( $key ))) {
		
			// если нет ответа - инициируем его
			$response = new \Response ();

			$action = $request->getAction();
			
			// нужно получить привилегии и проверить - есть ли у нас доступ
			$privileges = \Auth::normalizePrivileges ( $action->privileges () );
			
			// добавляем привилегии в ответ
			foreach ( $privileges as $pkey => $value ) {
				$response->offset ( 'privileges', $value, $pkey );
			}
			
			if (static::$currentContext->checkAccess ( $privileges )) {
				// доступ есть, делаем действие
				
				foreach ( $stages as $stage ) {
					if ($skipStages -- <= 0) {
						// выполняем действие, точнее его этап
						$response = $action->doStage ( $stage, $response );
						
						// накатываем изменения данных каналов из действий,
						// которые были выполнены внутри только что отыгранного
						
						$response->playChannels ();
					}
				}
				
				// выполняем что-нить по окончанию действия
//				$action->onFinish ();
	
			} else {				
				// накатываем тэги потому как в привилегиях тоже возможны зависимости
// 				$tags = (array)static::$currentContext->getTags();
// 				$response->append('tags', $tags);
// // 				foreach ($tags as $tag) {
// // 					$response->offset('tags', $tag, $tag);
// // 				}
				
				// доступа нет, модифицируем ответ соответствующим образом
				$response->setException ( new \Exception ( 'Access denied', 403 ) );
				
			}
			

			// тут берём из текущего контекста для текущего действия
			// и добавляем их в каналы
			$tags = (array)static::$currentContext->getTags();
			$response->append('tags', $tags);
			

			if (!$response->noCache()) {

				// если нет запрета кешировать
				static::toCache ( $key, $response );

			}
			
			// отчищаем кеш по тэгам, если надо
			// ушло в модель
//			\ActionCache::clean($cleanTags);
			
		}		

		return $response;
	}
	
	/**
	 *
	 * Сохраняем ответ в кеш
	 *
	 * 1) Вычиляем штамп (из данных ответа)
	 * 2) Достаём массив штампов из кеш с блокировкой
	 * 3) Модифицируем-создаём новый
	 * 4) Пишем данные по штампу
	 * 5) Пишем массив штампов
	 *
	 *
	 * @param string $key        	
	 * @param \Response $response        	
	 */
	static function toCache($key, $response) {
//		\Log::begin('toCache', 'toCache');
		// достаём из запроса привилегии и формируем штамп
		$privileges = (array)$response->get ( 'privileges' );
		$stamp = static::$currentContext->checkAccess ( $privileges, true ); // TODO: подумать про сортировку

		// получаем тэги для текущего действия, мержим их с остальными уже установленными тэгами
		$channelTags = (array)$response->get('tags');
		$tags = [];
		// мёржим все тэги
		foreach ($channelTags as $t) {
			$tags = array_merge($tags, $t);
		}
		//array_unique($tags);
		

		// достаём из кеша все привилегии и штампы
		$data = \ActionCache::get ( $key, false, true, true );
		if (empty ( $data )) {
			$data = [ 
					'privileges' => [ ], // массив всех встретившихся привилегий
					'stamps' => [ ], // комбинации привилегий и проверок - штампы
					'tags' => [ ], //(array)$tags //хитро мёржить
			];
		}
		
		// добавляем привилегии к общему списку
		foreach ( $privileges as $k => $value ) {
			$data ['privileges'] [$k] = $value;
		}
		
		// вычисляем ключ, добавляем штамп
		$stampKey = $key.md5(serialize($stamp));
		$data['stamps'][$stampKey] = $stamp;

		// сейвим ответ и массив штампов
		\ActionCache::set()
			->data($key, $data)
			->data($stampKey, $response, null, (array)$tags)
			->execute();
		
		//\Log::warn ( 'save-'.$key, $data );
//		\Log::end(static::$currentContext->getCurrentRequest()->action);	
	}
	
	/**
	 * Получаем данные из кеш
	 *
	 * 1) Получаем массив штампов из кеш, без блокировки, ожидая разблокировку
	 * 2) Ищем подходящий штамп
	 * 3) Если не находим - возвращаемся пустыми
	 * 4) Запрашиваем данные по найденному штампу с ожиданием разблокировки,
	 * грязными данным и блокировкой в случае отсутствия
	 * 5) Если нашли - возвращаем
	 * 6) Иначе мы уже заблокировали на запись при чтении
	 *
	 * @param unknown $key        	
	 */
	static function fromCache($key) {
		// достаём из кеша все привилегии и штампы
		$data = \ActionCache::get ( $key, false, true, false );
		
		//\Log::warn('load-'.$key, $data);
		
		if (!$data) {
			return null;
		}
		
		
		$privileges = $data['privileges'];
		$stamps = $data['stamps'];
		// готовим мегаштамп
		$megaStamp = static::$currentContext->checkAccess ( $privileges, true ); 
		// для каждого шштампа проверяем - проверяем наш ли это случай
		
		//\Log::warn('megastamp', $megaStamp);
		
		$intersectCount = -1; //перебираем все. ищем самое длинное совпадение...
		$dataKey = null;
		foreach ($stamps as $stampKey => $stamp) {
				$intersect = array_intersect_assoc($megaStamp, $stamp);
				$stampCount = count($stamp);
				if ($stampCount == count($intersect) && $stampCount > $intersectCount) {
					$intersectCount = $stampCount;
					$dataKey = $stampKey;
				}
		}
		
		//\Log::warn('datakey', $dataKey);
		
		// ничего не нашли
		if (null === $dataKey) {
			return null;
		}
		
		return \ActionCache::get($dataKey, false, true, true);
				
	}
}